#ifndef PLATEBASE_HPP
#define PLATEBASE_HPP

#include "digitbase.hpp"
#include <iostream>
#include <thread>
#include <mutex>
#include <analysisresult.hpp>

class PlateBase
{
public:

    static int GetCharIndex(char Character,
                            std::string vocabulary = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
            // must have the same order as the clasifier output classes
            )
    {
        size_t index=std::find(vocabulary.begin(),vocabulary.end(),Character)-vocabulary.begin();
        return index;
    }

public:
    unsigned int number;


    static cv::Ptr<cv::text::OCRHMMDecoder::ClassifierCallback> ocr;

    PlateBase()
        : number(10)
    {

    }


    static double ComputeNumberProperty(const std::vector<DigitBase> &candidate, std::vector<size_t> &index, unsigned int number)
    {
        unsigned int ge=number%10;
        unsigned int shi=number/10;

        std::vector<double> confidence;
        std::vector< std::vector<size_t> > nbbl;

        for(size_t i=0;i<candidate.size();i++)
        {
            for(size_t j=i+1;j<candidate.size();j++)
            {
                int res=candidate[i].IsCharacterLinked(candidate[j]);
                switch(res)
                {
                case 1:
                    nbbl.push_back(std::vector<size_t>(1,i));
                    nbbl.back().push_back(j);
                    confidence.push_back(candidate[i].GetNumberConfidence(shi)*candidate[j].GetNumberConfidence(ge));

                    //                    {cv::Mat canvas=inputImage.clone(); cv::rectangle(canvas, nl[i].boundingBox, cv::Scalar(0,0,255), 5); cv::rectangle(canvas, nl[j].boundingBox, cv::Scalar(0,255,0), 5); cv::imshow("r",canvas);cv::waitKey();}

                    break;
                case 2:
                    nbbl.push_back(std::vector<size_t>(1,j));
                    nbbl.back().push_back(i);
                    confidence.push_back(candidate[j].GetNumberConfidence(shi)*candidate[i].GetNumberConfidence(ge));

                    //                    {cv::Mat canvas=inputImage.clone(); cv::rectangle(canvas, nl[j].boundingBox, cv::Scalar(0,0,255), 5); cv::rectangle(canvas, nl[i].boundingBox, cv::Scalar(0,255,0), 5); cv::imshow("r",canvas);cv::waitKey();}

                    break;
                default:
                    break;
                }
            }
        }

        if(confidence.empty())
        {
            return 0;
        }


        size_t maxindex=std::max_element(confidence.begin(),confidence.end())-confidence.begin();

        index.assign(nbbl[maxindex].begin(), nbbl[maxindex].end());

        return confidence[maxindex];
    }


    static void ToBinary(cv::Mat &mask, const cv::Mat &inputImage, cv::Scalar hsvThres[2])
    {
        cv::Mat hsv;
        cv::cvtColor(inputImage, hsv, CV_BGR2HSV);

//        std::cout<<inputImage.size()<<"\n";

        //        cv::Mat mask;

        std::vector<cv::Mat> hsvl;
        cv::split(hsv, hsvl);

#if 1
        cv::Mat gray=hsvl[2];
        //        cv::cvtColor(inputImage, gray, CV_BGR2GRAY);



        cv::resize(gray, gray, cv::Size(), 0.5, 0.5);
        cv::Mat grayo;

        cv::Mat element25a = cv::getStructuringElement(cv::MORPH_ELLIPSE,cv::Size(100,100));
        cv::morphologyEx(gray,grayo,cv::MORPH_TOPHAT,element25a);

        cv::resize(grayo,grayo,inputImage.size());

        //         cv::inRange(grayo, cv::Scalar(hsvThres[0][2]), cv::Scalar(hsvThres[1][2]), mask);
        hsvl[2]=grayo;

#endif


        cv::merge(hsvl, hsv);


#if 1


        //        ExtractYellow(hsv,mask);

        //        cv::inRange(hsv, cv::Scalar(150,-1,150), cv::Scalar(180,256,255), mask);



        if(hsvThres[0][0]>hsvThres[1][0])
        {
            cv::Scalar ht0=hsvThres[0];
            cv::Scalar ht1=hsvThres[1];
            ht0[0]=-1;
            ht1[0]=181;

            cv::Mat mask0;
            cv::inRange(hsv, hsvThres[0], ht1, mask0);

            cv::Mat mask1;
            cv::inRange(hsv, ht0, hsvThres[1], mask1);

            cv::bitwise_or(mask0, mask1, mask);
        }
        else
            cv::inRange(hsv, hsvThres[0], hsvThres[1], mask);
#endif

#ifdef SHOWSEGMENT
        cv::imshow("bw",mask);
        cv::imshow("hsv",hsv);
#endif

    }


    static void AnlysisThread(std::vector<DigitBase> &nl, std::mutex &nlmtx, cv::Rect roi, const cv::Mat &image, int label)
    {
        DigitBase db(roi);
        db.label=label;

        db.Analysis(image, ocr);

        nlmtx.lock();
        nl.push_back(db);
        nlmtx.unlock();
    }

    static void FilterAnalysisCC(const cv::Mat &inputImage, cv::Mat &ccLabel, std::vector<DigitBase> &nl, cv::Scalar hsvThres[2], cv::Size rcThres=cv::Size(10,20))
    {
        cv::Mat bw,stat,centroid;
        ToBinary(bw, inputImage, hsvThres);

        cv::connectedComponentsWithStats(bw,ccLabel,stat,centroid);

        std::mutex mtx;

        std::vector<std::thread> ths;

        for (int i=1;i<stat.rows;i++)
        {
            cv::Rect rc(stat.at<int>(i,cv::CC_STAT_LEFT),stat.at<int>(i,cv::CC_STAT_TOP),stat.at<int>(i,cv::CC_STAT_WIDTH),stat.at<int>(i,cv::CC_STAT_HEIGHT));
            //            int area=stat.at<int>(i,cv::CC_STAT_AREA);
            if(rc.width>rcThres.width && rc.height>rcThres.height)
            {
                ths.push_back(std::thread(AnlysisThread, std::ref(nl), std::ref(mtx), rc, std::ref(inputImage), i));
            }
        }

        for(auto &th:ths)
            if(th.joinable())
                th.join();
    }

    double LocateTwoDigit(cv::Mat &inputImage, cv::Rect digitBoundingBox[2], cv::Mat bwDigit[2])
    {
        cv::Scalar hsvThres[2]={
            //            cv::Scalar(150,-1,150), cv::Scalar(180,256,255)
            cv::Scalar(-1,-1,110), cv::Scalar(256,256,255)
            //            cv::Scalar(150,-1,100), cv::Scalar(20,256,255)
            //            cv::Scalar(140,-1,50), cv::Scalar(40,256,255)
        };

        std::vector<DigitBase> nl;
        cv::Mat bw_label;

        int64 t0=cv::getTickCount();
        FilterAnalysisCC(inputImage, bw_label, nl, hsvThres);

        int64 t1=cv::getTickCount();

        std::cout<<"\nt="<<(double)(t1-t0)/cv::getTickFrequency()<<"\n"<<std::flush;

        std::vector<size_t> numberIndex;

        double confidence=ComputeNumberProperty(nl, numberIndex, number);

        if(confidence>0)
        {
            digitBoundingBox[0]=(nl[numberIndex[0]].boundingBox);

            digitBoundingBox[1]=(nl[numberIndex[1]].boundingBox);

            cv::compare(bw_label(digitBoundingBox[0]), nl[numberIndex[0]].label, bwDigit[0], cv::CMP_EQ);

            cv::compare(bw_label(digitBoundingBox[1]), nl[numberIndex[1]].label, bwDigit[1], cv::CMP_EQ);
        }

        return confidence;
    }

    static cv::Rect ExpandRoi(cv::Rect roi, cv::Size imageSize, cv::Size gap=cv::Size(45,45))
    {
        roi.x-=gap.width;
        if(roi.x<0)
            roi.x=0;

        roi.y-=gap.height;
        if(roi.y<0)
            roi.y=0;

        roi.width+=gap.width*2;
        int xborder=imageSize.width-roi.x;
        if(roi.width>xborder)
            roi.width=xborder;

        roi.height+=gap.height*2;
        int yborder=imageSize.height-roi.y;
        if(roi.height>yborder)
            roi.height=yborder;

        return roi;
    }

    double TraceTwoDigit(cv::Mat &inputImage, cv::Rect digitBoundingBox[2], cv::Mat bwDigit[2], cv::Size gap=cv::Size(30,30))
    {
        cv::Rect digitSearchRegion[2]={
            ExpandRoi(digitBoundingBox[0], inputImage.size(), gap),
            ExpandRoi(digitBoundingBox[1], inputImage.size(), gap)
        };

        cv::Rect roi=digitSearchRegion[0]|digitSearchRegion[1];

        cv::Mat bgr=inputImage(roi);

        double res=LocateTwoDigit(bgr, digitBoundingBox, bwDigit);

        if(res>0)
        {
            digitBoundingBox[0].x+=roi.x;
            digitBoundingBox[0].y+=roi.y;

            digitBoundingBox[1].x+=roi.x;
            digitBoundingBox[1].y+=roi.y;
        }

        return res;
    }

    void test(std::string path)
    {
        cv::Mat m=cv::imread(path);

        AnalysisResult ar;

        ar.property=LocateTwoDigit(m, ar.roiDigit, ar.bwDigit);
    }
};



cv::Ptr<cv::text::OCRHMMDecoder::ClassifierCallback> PlateBase::ocr = cv::text::loadOCRHMMClassifierCNN("OCRBeamSearch_CNN_model_data.xml.gz");


#endif // PLATEBASE_HPP

